Date:17 March 2023
This is the open data/notebook for the article Accelerating Winter Security of Supply Problems. The article can be reached from (barissanli.com)
Main question is the dangers posed by relying on solar for winter security of supply based on aggregated annual data. If, one looks in detail, solar is practically not existent in winter during highest peak demand periods, while a great saviour for the rest of the year. Averaging the data shows clear benefits. But dissecting the data, hints about looming winter security of supply crisis on a much more solar based system
Data is from
Ember monthly data is used for European countries. Through this dataset, solar's share in demand is graphed. EPİAŞ data is used for monthly capacity factor for Turkish solar generation part. If 10000 MW's of solar is installed in the grid, it is practically 5000 MW base load equivalent for summer, but 1600 MW base load equivalent for winter. So replacing thermal resources with renewable has an asymmetric effect seasonally.
Python libraries needed are loaded
%matplotlib inline
import matplotlib.pyplot as plt
import pandas as pd
import seaborn
Larger graph areas
plt.rcParams["figure.figsize"]=[12.0,6.0]
First download Ember Monthly electricity data
import requests
from urllib.request import urlopen
link="https://ember-climate.org/app/uploads/2022/07/monthly_full_release_long_format-2.csv"
# orjinal kod : https://stackoverflow.com/questions/65020294/downloading-a-zipped-file-using-python-3-7
def download_url(url, save_path, chunk_size=128):
r = requests.get(url, stream=True)
with open(save_path, 'wb') as fd:
for chunk in r.iter_content(chunk_size=chunk_size):
fd.write(chunk)
#ilk sefer indir, ikinci çalışmada tekrar tekrar indirmeye gerek kalmasın
download_url(link,"./ember.csv")
Read the data from downloaded file
df = pd.read_csv('ember.csv')
df.head(2)
Area | Country code | Date | Area type | Continent | Ember region | EU | OECD | G20 | G7 | Category | Subcategory | Variable | Unit | Value | YoY absolute change | YoY % change | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Argentina | ARG | 2018-01-01 | Country | South America | Latin America and Caribbean | 0.0 | 0.0 | 1.0 | 0.0 | Electricity demand | Demand | Demand | TWh | 12.77 | NaN | NaN |
1 | Argentina | ARG | 2018-01-01 | Country | South America | Latin America and Caribbean | 0.0 | 0.0 | 1.0 | 0.0 | Electricity generation | Aggregate fuel | Clean | % | 34.57 | NaN | NaN |
Ember data is monthly, but python sees the data as Year-Month-Day so we first convert it to datetime object, followed by Year-Month conversion
from datetime import datetime
df['Date']=(pd.to_datetime(df['Date'], format="%Y-%m-%d")).dt.strftime('%Y-%m')
df=df[df['Date']>"2017-01"]
df.head(2)
Area | Country code | Date | Area type | Continent | Ember region | EU | OECD | G20 | G7 | Category | Subcategory | Variable | Unit | Value | YoY absolute change | YoY % change | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Argentina | ARG | 2018-01 | Country | South America | Latin America and Caribbean | 0.0 | 0.0 | 1.0 | 0.0 | Electricity demand | Demand | Demand | TWh | 12.77 | NaN | NaN |
1 | Argentina | ARG | 2018-01 | Country | South America | Latin America and Caribbean | 0.0 | 0.0 | 1.0 | 0.0 | Electricity generation | Aggregate fuel | Clean | % | 34.57 | NaN | NaN |
seaborn.set_theme()
In most of the analysis, we will use share in demand. Because we want to observe how solar satisfies demand. However, in some countries the two data makes a difference.
Therefore we first define a function that can plot both % share and TWh generation in the same graph for two countries or for a single country
is used.
def twoCountries_sameAxis(c1,c2,ctip="%"):
ulke=df[(df.Area==c1) & (df.Variable=='Solar') & (df.Unit==ctip) ];
plt.plot(ulke.Date,ulke.Value, label=c1);
ulke=df[(df.Area==c2) & (df.Variable=='Solar') & (df.Unit==ctip) ];
plt.plot(ulke.Date,ulke.Value, label=c2);
plt.xticks(rotation=90, fontsize=9);
plt.title(c1+" vs "+c2+ " - Solar Generation("+ctip+")");
plt.ylabel("("+ctip+")")
plt.legend();
def twoAxis(c1):
fig, ax1 = plt.subplots()
ax1.tick_params(labelrotation=90)
ulke=df[(df.Area==c1) & (df.Variable=='Solar') & (df.Unit=="%") ];
ax1.plot(ulke.Date,ulke.Value, label="%", color='b', alpha=0.5);
ax2 = ax1.twinx()
ulke=df[(df.Area==c1) & (df.Variable=='Solar') & (df.Unit=="TWh") ];
ax2.plot(ulke.Date,ulke.Value, label="TWh", color='r', alpha=0.5);
fig.legend();
ax1.set_xlabel('Date')
ax1.set_ylabel('Share of solar in demand (Percentage %)', color='b')
ax2.set_ylabel('TWh generation of solar', color='r')
plt.title(c1+ " Solar Generation - Share in Demand(%) vs Generation(TWh)")
#fig.xticks(rotation=90, fontsize=9);
#plt.ylabel("("+ctip+")")
Now let's compare Germany as % of demand and TWh of generation. Since Germany's demand is relatively stable, they are close
twoAxis("Germany")
But for a country like Türkiye, the data may change. In Turkish data, effect of Covid 19 policies are seen as a spike in blue (% - percent) line.
twoAxis("Turkey")
But if we compare two countries like China and Germany, % comparison and TWh comparison makes a lot of difference. For example in the following graph, Germant and China is compared in terms of solar generation as % share of demand.
twoCountries_sameAxis('Germany','China',"%")
However, if we compare this two countries in absolute solar generation, the results are vastly different. One of the important factors is the growing demand of China, dampens % share of solar generation, despite higher generation levels
twoCountries_sameAxis('Germany','China',"TWh")
In the following graph, please check the difference in the curve structure. Spain's both winter and summer values are increasing, while for Germany winter is pretty static, lower than 2.5%
twoCountries_sameAxis("Spain","Germany")
Italy vs Spain is another interesting example. Italy behaves more like Germany in Winter. However Spain has an obviously increasing winter share
twoCountries_sameAxis("Spain","Italy")
Another interesting example is Poland vs Türkiye. Poland has increased its solar generation tremendously, but winter-summer inbalance is much higher than Türkiye
twoCountries_sameAxis("Poland","Turkey")
Denmark and Netherlands is another interesting case. It clearly shows the gravity of low solar share in generation during winter
twoCountries_sameAxis("Denmark","Netherlands")
Germany and UK is another interesting example.
twoCountries_sameAxis("Germany", "United Kingdom")
df.Area.unique()
array(['Argentina', 'Asia', 'Australia', 'Austria', 'Bangladesh', 'Belgium', 'Bolivia', 'Bosnia Herzegovina', 'Brazil', 'Bulgaria', 'Burundi', 'Canada', 'Chile', 'China', 'Colombia', 'Costa Rica', 'Croatia', 'Cyprus', 'Czechia', 'Denmark', 'Ecuador', 'Egypt', 'El Salvador', 'Estonia', 'EU', 'Europe', 'Finland', 'France', 'G20', 'G7', 'Georgia', 'Germany', 'Greece', 'Hungary', 'India', 'Iran (Islamic Republic of)', 'Ireland', 'Italy', 'Japan', 'Kenya', 'Kosovo', 'Latin America and Caribbean', 'Latvia', 'Lithuania', 'Luxembourg', 'Malta', 'Mexico', 'Moldova', 'Mongolia', 'Montenegro', 'Netherlands', 'New Zealand', 'Nigeria', 'North America', 'North Macedonia', 'Norway', 'OECD', 'Oman', 'Pakistan', 'Peru', 'Philippines (the)', 'Poland', 'Portugal', 'Qatar', 'Romania', 'Russian Federation (the)', 'Serbia', 'Singapore', 'Slovakia', 'Slovenia', 'South Africa', 'South Korea', 'Spain', 'Sweden', 'Switzerland', 'Taiwan', 'Thailand', 'Tunisia', 'Turkey', 'Ukraine', 'United Kingdom', 'United States of America', 'Uruguay', 'Viet Nam', 'World'], dtype=object)
twoCountries_sameAxis("China","India")
twoCountries_sameAxis("India","Viet Nam")
#twoCountries_sameAxis("Brazil","Viet Nam")
There are complimentary countries as well Such as Australia and Germany
twoCountries_sameAxis("Australia","Germany")
The following process merges two data
This is hourly Turkish electricity generation data:
elektrik=pd.read_pickle("2021-2022-elektrik.pickle")
Now, EPDK's monthly solar installation numbers are loaded
a=pd.read_csv("gunes.csv",decimal=",",delimiter=";").set_index("ay")
a.index = pd.to_datetime(a.index)
a.index=a.index.to_period('M').to_timestamp('M')
a.index.name="date"
In this part, licensed(sun) and unlicensed(lsun) is summed. Total generation is licensed-utility scale (total) and unlicensed(ltotal - well sort of distributed) generation
elektrik["toplam"]=elektrik.total+elektrik.ltotal
elektrik["gunes"]=elektrik.sun+elektrik.lsun
saatlik_uretim=elektrik[["date","gunes","toplam"]].set_index("date")
The hourly data is converted to daily data
gunluk_uretim=saatlik_uretim.groupby(pd.Grouper(freq='D')).sum()
Solar Installed Capacity Growth is plotted
plt.plot(a);
plt.title("Türkiye's Solar Installed Capacity Growth");
plt.ylabel("MW");
This part may look complicated. But practically, we merge monthly installed capacity data with daily generation data.
brl=pd.merge(gunluk_uretim,a, how="outer",on="date")
brl_int=brl.interpolate(method ='linear', limit_direction ='forward').interpolate(method ='linear', limit_direction ='backward')
brl_int["gunluk"]=brl_int["gunesMW"]*24
brl_int["cf"]=brl_int["gunes"]/brl_int["gunluk"]
In this part, we will zoom the data in daily instances to see seasonal and daily fluctuations First, daily and 30 days moving average solar capacity factor is plotted
plt.plot(100*brl_int["cf"],linewidth=1,label="Solar Capacity Factor")
plt.plot(100*brl_int["cf"].rolling(30).mean(),linewidth=3, color="red", label="MA(30)")
plt.legend();
plt.title("Türkiye - Solar's daily capacity factor ");
plt.xlabel("Date")
plt.ylabel("Percentage(%)")
Text(0, 0.5, 'Percentage(%)')
plt.hist(sorted(brl_int["cf"]*100),bins=30);
plt.xlabel("Capacity Factor")
plt.ylabel("Number of Days")
plt.title("The Histogram of Solar Capacity Factor")
Text(0.5, 1.0, 'The Histogram of Solar Capacity Factor')
sonyil=brl_int
Türkiye's summer demand is generally higher than winter, due to coooling load. But if you look at the red line, there is also a winter peak. The gross demand in winter coincides with lowest daily capacity factors for solar. But we will have a more professional graph in the following line
plt.plot(sonyil.cf*100, label="Capacity Factor of Solar(%)")
plt.plot(sonyil.toplam/25000,label="Total Gross Demand")
[<matplotlib.lines.Line2D at 0x16df562b0>]
Now, the data is dissected from bas(head) to 120 days forward. Because on 2021, Dec 15, a cold, snowy weather hit most of the Türkiye. You can see the effect of snow on blue line for 2021-12-15. Also check the red line as demand data. While demand is peaking, capacity factor is going down
bas=300
son=bas+120
fig, ax1 = plt.subplots()
ax1.plot(100*sonyil.cf[bas:son], alpha=0.5)
ax2 = ax1.twinx()
ax2.plot(sonyil.toplam[bas:son]/1000, color="red", alpha=0.5)
ax2.set_ylim([6e2, 1e3])
ax1.set_ylim([2.5, 30])
ax1.set_xlabel('Date')
ax1.set_ylabel('Solar Capacity Factor (Percentage %)', color='b')
ax2.set_ylabel('GWh - Gross Electricity Demand', color='r')
plt.title("Türkiye - Solar Capacity Factor vs Electricity Demand - Kış Dönemi")
Text(0.5, 1.0, 'Türkiye - Solar Capacity Factor vs Electricity Demand - Kış Dönemi')
We can even zoom more
bas=345
son=bas+45
fig, ax1 = plt.subplots()
ax1.plot(100*sonyil.cf[bas:son], alpha=0.5)
ax2 = ax1.twinx()
ax2.plot(sonyil.toplam[bas:son]/1000, color="red", alpha=0.5)
ax2.set_ylim([6e2, 1.01e3])
ax1.set_ylim([2.5, 30])
ax1.set_xlabel('Date')
ax1.set_ylabel('Solar Capacity Factor (Percentage %)', color='b')
ax2.set_ylabel('GWh - Gross Electricity Demand', color='r')
plt.title("Türkiye - Solar Capacity Factor vs Electricity Demand - Dec-Jan 2021")
100*sonyil.cf[bas:son].mean()
sonyil.toplam[bas:son].mean()
927288.9311111112
Neverthless, solar is extremely helpful from April to October. So solar capacity factor is quite reliable for 6 months
bas=420
son=bas+210
fig, ax1 = plt.subplots()
ax1.plot(100*sonyil.cf[bas:son], alpha=0.5)
ax2 = ax1.twinx()
ax2.plot(sonyil.toplam[bas:son]/1000, color="red", alpha=0.5)
ax2.set_ylim([5e2, 1.1e3])
ax1.set_ylim([5, 60])
ax1.set_xlabel('Date')
ax1.set_ylabel('Solar Capacity Factor (Percentage %)', color='b')
ax2.set_ylabel('GWh - Gross Electricity Demand', color='r')
plt.title("Türkiye - Solar Capacity Factor vs Electricity Demand - Summer")
Text(0.5, 1.0, 'Türkiye - Solar Capacity Factor vs Electricity Demand - Summer')
Now, if we look at the whole data set, 2022 January is an interesting point to observe
fig, ax1 = plt.subplots()
ax1.plot(100*sonyil.cf)
ax2 = ax1.twinx()
ax2.plot(sonyil["toplam"]/1000, color="red", alpha=0.5)
ax2.set_ylim([6e2, 1.3e3])
ax1.set_xlabel('Tarih')
ax1.set_ylabel('Günlük Kapasite Faktörü (% yüzde)', color='b')
ax2.set_ylabel('GWh Elektrik Üretimi', color='r')
plt.title("Elektrik Üretimi ile Güneş Kapasite Faktörünün hareketi");
As a last point, we should see the decreasing correlation in winter and increasing one in summer. The two dips are religious holidays in Türkiye.
cor=saatlik_uretim["gunes"].rolling(7*24).corr(saatlik_uretim["toplam"])
plt.plot(cor)
cor=saatlik_uretim["gunes"].rolling(7*24).corr(saatlik_uretim["toplam"]).rolling(24*7).mean()
plt.plot(cor, color="red", linewidth=3)
plt.xlabel("Date")
plt.ylabel("Correlation")
plt.title("Solar Generation and Total Generation's hourly correlation on a 7 day basis");